using System;
using System.Data.SqlClient;
using System.Text;
using System.Text.RegularExpressions;

using ObjectRelationalMapping.Helper;
using ObjectRelationalMapping.Model;

namespace ObjectRelationalMapping
{
    internal static partial class Mapper
    {
        internal static class Class
        {
            internal static void Generate ( )
            {
                if ( Common.EntityCollection.Count > 0 )
                {
                    Console.Write ( "Generating the class objects..." );
                    int numberOfClassObjects = 0;

                    foreach ( Entity entity in Common.EntityCollection )
                    {
                        entity.ClassTemplate =
                            Regex.Replace (
                                Common.TemplateForClassObjects,
                                Common.Constants.UserDefinedClassName,
                                entity.ClassName );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedApplicationNamespace,
                                Common.ApplicationNamespace );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedProperty,
                                GenerateClassProperties ( entity ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedVariable,
                                GenerateClassVariables ( entity ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedDataReaderAssignments,
                                GenerateDataReaderAssignments ( entity ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedNonPrimaryKeyParameter,
                                GenerateSqlParameters (
                                    true,
                                    entity.Metaclass.Properties.CountOfNonPrimaryKeyProperties,
                                    entity.Metaclass.Properties.NonPrimaryKeyProperties,
                                    entity.Metadata.Properties.NonPrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedParameter,
                                GenerateSqlParameters (
                                    true,
                                    entity.Metaclass.Properties.Count,
                                    entity.Metaclass.Properties,
                                    entity.Metadata.Properties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedPrimaryKeyParameter,
                                GenerateSqlParameters (
                                    true,
                                    entity.Metaclass.Properties.CountOfPrimaryKeyProperties,
                                    entity.Metaclass.Properties.PrimaryKeyProperties,
                                    entity.Metadata.Properties.PrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedFetchMethodParameter,
                                GenerateMethodParameters (
                                    entity.Metaclass.Properties.CountOfPrimaryKeyProperties,
                                    entity.Metaclass.Properties.PrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedFetchSqlParameter,
                                GenerateSqlParameters (
                                    false,
                                    entity.Metaclass.Properties.CountOfPrimaryKeyProperties,
                                    entity.Metaclass.Properties.PrimaryKeyProperties,
                                    entity.Metadata.Properties.PrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedInsertMethodParameter,
                                GenerateMethodParameters (
                                    entity.Metaclass.Properties.CountOfNonPrimaryKeyProperties,
                                    entity.Metaclass.Properties.NonPrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedInsertSqlParameter,
                                GenerateSqlParameters (
                                    false,
                                    entity.Metaclass.Properties.CountOfNonPrimaryKeyProperties,
                                    entity.Metaclass.Properties.NonPrimaryKeyProperties,
                                    entity.Metadata.Properties.NonPrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedRemoveMethodParameter,
                                GenerateMethodParameters (
                                    entity.Metaclass.Properties.CountOfPrimaryKeyProperties,
                                    entity.Metaclass.Properties.PrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedRemoveSqlParameter,
                                GenerateSqlParameters (
                                    false,
                                    entity.Metaclass.Properties.CountOfPrimaryKeyProperties,
                                    entity.Metaclass.Properties.PrimaryKeyProperties,
                                    entity.Metadata.Properties.PrimaryKeyProperties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedUpdateMethodParameter,
                                GenerateMethodParameters (
                                    entity.Metaclass.Properties.Count,
                                    entity.Metaclass.Properties ) );
                        entity.ClassTemplate =
                            Regex.Replace (
                                entity.ClassTemplate,
                                Common.Constants.UserDefinedUpdateSqlParameter,
                                GenerateSqlParameters (
                                    false,
                                    entity.Metaclass.Properties.Count,
                                    entity.Metaclass.Properties,
                                    entity.Metadata.Properties ) );

                        ++numberOfClassObjects;
                    }

                    Console.WriteLine (
                        "and {0} class objects were generated.",
                        numberOfClassObjects );
                }
            }

            private static string GenerateClassProperties ( Entity entity )
            {
                StringBuilder result = new StringBuilder ();

                foreach ( Property<Metaclass> property
                    in entity.Metaclass.Properties )
                {
                    string temporaryTemplate =
                        Common.TemplateForClassProperties;
                    temporaryTemplate =
                        Regex.Replace (
                            temporaryTemplate,
                            Common.Constants.UserDefinedProperty,
                            property.Name );
                    temporaryTemplate =
                        Regex.Replace (
                            temporaryTemplate,
                            Common.Constants.UserDefinedType,
                            property.Type );
                    temporaryTemplate =
                        Regex.Replace (
                            temporaryTemplate,
                            Common.Constants.UserDefinedVariable,
                            property.Name );
                    result.Append ( "\r\n" );
                    result.Append ( temporaryTemplate );
                }

                return result.ToString ();
            }

            private static string GenerateClassVariables ( Entity entity )
            {
                StringBuilder result = new StringBuilder ();

                foreach ( Property<Metaclass> property
                    in entity.Metaclass.Properties )
                {
                    result.Append ( "\t\tprivate " );
                    result.Append ( property.Type );
                    result.Append ( " _" );
                    result.Append ( property.Name );
                    result.Append ( ";\r\n" );
                }

                return result.ToString ();
            }

            private static string GenerateDataReaderAssignments (
                Entity entity )
            {
                StringBuilder result = new StringBuilder ();

                int numberOfProperties = entity.Metaclass.Properties.Count - 1;
                for ( int index = 0; index <= numberOfProperties; ++index )
                {
                    Property<Metaclass> property =
                        entity.Metaclass.Properties[ index ];
                    result.Append ( "\t\t\t\t\t\t" );
                    result.Append ( "entity." );
                    result.Append ( property.Name );
                    result.Append ( " =\r\n\t\t\t\t\t\t\t" );
                    result.Append ( "( dataReader.IsDBNull ( " );
                    result.Append ( index.ToString () );
                    result.Append ( " )\r\n\t\t\t\t\t\t\t\t" );
                    result.Append ( "? " );
                    result.Append ( property.GetEmptyDataReaderValue () );
                    result.Append ( " : dataReader.Get" );
                    result.Append ( property.GetDataReaderType () );
                    result.Append ( " ( " );
                    result.Append ( index.ToString () );
                    result.Append ( " ) );\r\n" );
                }

                return result.ToString ();
            }

            private static string GenerateMethodParameters (
                int numberOfProperties,
                PropertyCollection<Metaclass> properties )
            {
                StringBuilder result = new StringBuilder ();

                numberOfProperties -= 1;
                for ( int index = 0; index <= numberOfProperties; ++index )
                {
                    Property<Metaclass> property = properties[ index ];
                    string propertyName =
                        property.Name.Substring ( 0, 1 ).ToLower ()
                        + property.Name.Substring ( 1 );
                    result.Append ( "\t\t\t" );
                    result.Append ( property.Type );
                    result.Append ( " " );
                    result.Append ( propertyName );
                    if ( index < numberOfProperties
                        && numberOfProperties != 0 )
                    {
                        result.Append ( ",\r\n" );
                    }
                }

                return result.ToString ();
            }

            private static string GenerateNullableSqlParameter (
                string classPropertyName,
                string classPropertyType,
                string databasePropertyName,
                bool includeThisKeyword,
                bool isNullable )
            {
                StringBuilder result = new StringBuilder ();

                if ( !includeThisKeyword )
                {
                    classPropertyName =
                        classPropertyName.Substring ( 0, 1 ).ToLower ()
                        + classPropertyName.Substring ( 1 );
                }

                result.Append ( "\t\t\t\t\tnew SqlParameter ( \"@" );
                result.Append ( databasePropertyName );
                result.Append ( "\", " );
                if ( isNullable )
                {
                    result.Append ( "( " );
                }
                if ( includeThisKeyword )
                {
                    result.Append ( "this." );
                }
                result.Append ( classPropertyName );
                if ( isNullable )
                {
                    switch ( classPropertyType )
                    {
                        case "DateTime":
                            result.Append ( " <= ( DateTime )SqlDateTime.MinValue ? SqlDateTime.Null : " );
                            break;
                        case "int":
                            result.Append ( " == 0 ? SqlInt32.Null : " );
                            break;
                        case"short":
                            result.Append ( " == 0 ? SqlInt16.Null : " );
                            break;
                        case "string":
                            result.Append ( " == string.Empty ? SqlString.Null : " );
                            break;
                    }
                    if ( includeThisKeyword )
                    {
                        result.Append ( "this." );
                    }
                    result.Append ( classPropertyName );
                }
                if ( isNullable )
                {
                    result.Append ( " )" );
                }
                result.Append ( " )" );

                return result.ToString ();
            }

            private static string GenerateSqlParameters (
                bool includeThisKeyword,
                int numberOfProperties,
                PropertyCollection<Metaclass> classProperties,
                PropertyCollection<Metadata> databaseProperties )
            {
                StringBuilder result = new StringBuilder ();

                numberOfProperties -= 1;
                for ( int index = 0; index <= numberOfProperties; ++index )
                {
                    Property<Metaclass> classProperty = 
                        classProperties[ index ];
                    Property<Metadata> databaseProperty =
                        databaseProperties[ index ];
                    result.Append (
                        GenerateNullableSqlParameter (
                            classProperty.Name,    
                            classProperty.Type,
                            databaseProperty.Name,
                            includeThisKeyword,
                            classProperty.IsNullable ) );
                    if ( index < numberOfProperties
                        && numberOfProperties != 0 )
                    {
                        result.Append ( ",\r\n" );
                    }
                }

                return result.ToString ();
            }
        }
    }
}